package org.dandsoft.blackbox.laboratory.tests;

import org.dandsoft.blackbox.counter.RelationshipCounter;

/**
 * Производит определение взаимосвязи между аргументами и значением функции.
 * Посути просто находит функцию по аргументу и результату.
 * Есть группы событий, при определенных комбинациях состояний этих сигналов функция активна
 * или нет. Задача имея более избыточные данные отбросить не нужные  и сформировать правильную
 * реакцию на аргументы
 *
 * @author Vilyuzhanin Denis (Fin)
 */
public class Relationship {

    private int signalsCount;
    private int noiseSignalsCount;
    private byte[][] function;
    private double functionPower;
    private boolean isInversion;

    public Relationship(boolean isInversion, double power, int signalsCount, int noiseSignalsCount) {
        this.signalsCount = signalsCount;
        this.noiseSignalsCount = noiseSignalsCount;
        this.functionPower = power;
        this.isInversion = isInversion;
        initializeRandomFunction();
    }

    public Relationship(boolean isInversion, byte[][] function, int noiseSignalsCount) {
        this.function = function;
        this.signalsCount = function[0].length;
        this.isInversion = isInversion;
        this.noiseSignalsCount = noiseSignalsCount;
    }

    public void initializeRandomFunction() {
        int resultsCount = (int) (functionPower * Math.pow(2, signalsCount) / 100D);
        if (resultsCount == 0) {
            resultsCount = 1;
        }

        function = new byte[resultsCount][signalsCount];
        for (int i = 0; i < function.length; i++) {
            for (int j = 0; j < function[i].length; j++) {
                if (Math.random() * 2 < 1) {
                    function[i][j] = 0;
                } else {
                    function[i][j] = 1;
                }
            }
        }
    }


    private void generateFunctionArguments(byte[] arguments) {
        for (int i = 0; i < signalsCount; i++) {
            if (Math.random() * 2 < 1) {
                arguments[i] = 0;
            } else {
                arguments[i] = 1;
            }
        }
    }

    private byte getFunctionValue(byte[] arguments) {
        for (int i = 0; i < function.length; i++) {
            boolean isOk = true;
            for (int j = 0; j < signalsCount; j++) {
                if (arguments[j] != function[i][j]) {
                    isOk = false;
                    break;
                }
            }
            if (isOk) {
                if (isInversion) {
                    return 0;
                } else {
                    return 1;
                }
            }
        }

        if (isInversion) {
            return 1;
        } else {
            return 0;
        }
    }

    private void generateNoise(byte[] arguments) {
        for (int i = signalsCount; i < signalsCount + noiseSignalsCount; i++) {
            if (Math.random() * 2 < 1) {
                arguments[i] = 0;
            } else {
                arguments[i] = 1;
            }
        }
    }

    private RelationshipCounter[] createCounters() {
        RelationshipCounter[] counters = new RelationshipCounter[signalsCount + noiseSignalsCount];
        for (int i = 0; i < counters.length; i++) {
            counters[i] = new RelationshipCounter();
        }
        return counters;
    }

    public void run(int steps) {
        byte[] signals = new byte[signalsCount + noiseSignalsCount];
        RelationshipCounter[] countes = createCounters();
        for (int time = 0; time < steps; time++) {
            generateFunctionArguments(signals);
            generateNoise(signals);
            byte result = getFunctionValue(signals);

            for (int i = 0; i < countes.length; i++) {
                RelationshipCounter counter = countes[i];
                byte signal = signals[i];
                if (result == 0 && signal == 0) {
                    counter.incrementNormalPassive();

                } else if (result == 0 && signal == 1) {
                    counter.incrementInversionPassive();

                } else if (result == 1 && signal == 0) {
                    counter.incrementInversionActive();

                } else if (result == 1 && signal == 1) {
                    counter.incrementNormalActive();
                }
            }
        }

        for (int i = 0; i < countes.length; i++) {
            System.out.print(countes[i].toString());
            if (i >= signalsCount) {
                System.out.print(" NOISE");
            }
            System.out.println();
        }
    }

    public void printFunction() {
        for (int i = 0; i < function.length; i++) {
            System.out.print("[ ");
            for (int j = 0; j < function[i].length; j++) {
                System.out.print(function[i][j] + ", ");
            }
            System.out.print(" ]");
            if (isInversion) {
                System.out.println(" = 0");
            } else {
                System.out.println(" = 1");
            }
        }
    }


    public static void main(String[] args) throws Exception {
        one();
        oneInvers();
        two();
        twoOne();
        three();
        fourAnd();
        image4();
        image4Inverse();
        veryLongImage();
    }

    public static void one() {
        System.out.println("====ONE STARTED====");
        byte[][] function = {{1}};
        Relationship relationship = new Relationship(false, function, 20);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====ONE FINISHED====");
    }

    public static void oneInvers() {
        System.out.println("====ONE INVERS STARTED====");
        byte[][] function = {{1}};
        Relationship relationship = new Relationship(true, function, 20);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====ONE INVERS FINISHED====");
    }

    public static void two() {
        System.out.println("====TWO STARTED====");
        byte[][] function = {{1, 0}, {0, 1}};
        Relationship relationship = new Relationship(false, function, 20);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====TWO FINISHED====");
    }

    public static void twoOne() {
        System.out.println("====TWO-ONE STARTED====");
        byte[][] function = {{1, 0}, {1, 1}};
        Relationship relationship = new Relationship(false, function, 20);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====TWO-ONE FINISHED====");
    }

    public static void three() {
        System.out.println("====THREE STARTED====");
        byte[][] function = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
        Relationship relationship = new Relationship(false, function, 20);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====THREE FINISHED====");
    }


    public static void fourAnd() {
        System.out.println("====AND FOUR STARTED====");
        byte[][] function = {{1, 1, 1, 1}};
        Relationship relationship = new Relationship(false, function, 5);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====AND FOUR FINISHED====");
    }

    public static void image4() {
        System.out.println("====IMAGE 4 STARTED====");
        byte[][] function = {{0, 1, 0, 1}, {1, 1, 1, 0}};
        Relationship relationship = new Relationship(false, function, 5);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====IMAGE 4 FINISHED====");
    }

    public static void image4Inverse() {
        System.out.println("====IMAGE 4 INVERS STARTED====");
        byte[][] function = {{0, 1, 0, 1}};
        Relationship relationship = new Relationship(true, function, 5);
        relationship.printFunction();
        relationship.run(100);
        System.out.println("====IMAGE 4 INVERS FINISHED====");
    }


    public static void veryLongImage() {
        System.out.println("====VERY LONG IMAGE STARTED====");
        final int SIZE = 5;
        byte[][] function = new byte[1][SIZE];
        for (int i = 0; i < SIZE; i++) {
            if (Math.random() * 2 < 1) {
                function[0][i] = 1;
            } else {
                function[0][i] = 0;
            }
        }

        Relationship relationship = new Relationship(false, function, 10);
        relationship.printFunction();
        relationship.run(SIZE * (int) Math.pow(SIZE, 3));
        System.out.println("====VERY LONG IMAGE FINISHED====");
    }

}
